// ===================================================
// 
// Tree structure for Mongoose Schema data models that 
// have parent-child relationship.
// 
// It use 'subnodes' for storing child nodes
// 
// function: addChild(parent, child):
//    for adding child whose parent is specified.
//
// function: findParent(parent):
//    for searching for the node in the tree whose
//    identity is specified.
//
// function: size():
//    for returning the size of the tree.
//
// function: getRoot():
//    for retrieve the root node of the tree.
// 
// Author: Kevin Zhuang 09/20/2014
// version 1.0.0.2
// ===================================================
function MongoTree(n) {
   var root = n;
   if(!root.subnodes) root.subnodes = [];
   
   // Insert child node into the tree whose parent is specified.
   this.addChild = function(parent, child) {
      if(!child.subnodes) child.subnodes = [];
      var node = this.findParent(parent);
      if(node) {
         node.subnodes.push(child);
         
      } else {
         if(!parent.subnodes) parent.subnodes = [];
         parent.subnodes.push(child);
         root.subnodes.push(parent);
      }
   }
   
   // Returning the tree node whose identity is specified.
   this.findParent = function(parent) {
      var que = [];
      que.push(root);
      while(0 < que.length) {
         var node = que.shift();
         if (node._id && node._id.toString() === parent._id.toString()) {
            
            return node;
            
         } else {
            node.subnodes.forEach(function(subnode) {
               que.push(subnode);
            });
         }
      }
      
      return null;
   }
   
   // Returning the root tree node.
   this.getRoot = function() {
      return root;
   };
   
   
   // Returning the size of the tree.
   this.size = function() {
      var count = 0;
      var que = [];
      que.push(root);
      while(0 < que.length) {
         var node = que.shift();
         node.subnodes.forEach(function(subnode) {
            que.push(subnode);
         });
         count++;
      }

      return count;
   }
   
}   


// Retrieving subnodes from collections for name-specified parentNode
//
// @param docs             - collection of Mongoose schema objects
// @param parentNode       - root node that return collection nodes are parent at.
// @param parentNodeName   - name of the 'parent' field in the schema.
//
// Calling it from outside:
//    var MongoTree = require('./MongoTree');
//    var docs = MongoTree.getSubnodes(docs, node, 'parent');
//
MongoTree.getSubnodes = function(docs, parentNode, parentNodeName) {
   var values = [];
   if(docs) {
      docs.forEach(function(doc) {
         if(doc[parentNodeName] && doc[parentNodeName]._id.toString() === parentNode._id.toString()) {
            values.push(doc);
         }
      });
   }
   
   return values;
};   

// Turning a collections into a tree. and return the root.
// 
// @param docs             - collection of Mongoose schema objects
// @param parentNode       - root node that return collection nodes are parent at.
// @param parentNodeName   - name of the 'parent' field in the schema.
//
// Calling it from outside:
//    var MongoTree = require('./MongoTree');
//    var tree = MongoTree.getTreeifiedRoot(docs, node, 'parent');
//
MongoTree.getTreeifiedRoot = function(docs, parentNode, parentNodeName) {
   var tree = null;
   if(parentNode) {
      tree = new MongoTree(parentNode);
      
      var que = [];
      que.push(parentNode);
      while(0 < que.length) {
         var p = que.shift();
         var vs = MongoTree.getSubnodes(docs, p, parentNodeName);
         vs.forEach(function(v) {
            if(p._id.toString() !== v._id.toString()) {
               que.push(v);
               tree.addChild(p, v);
            }
         });
      }
   }
   return tree ? tree.getRoot() : null;
}

module.exports = MongoTree;